{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3.5. 将课程内容转为视频\n",
    "\n",
    "## 🚄 前言\n",
    "通过整合之前生成的文字、音频、PPT，我们能够制作出引人入胜的科普课程视频，使表达更加生动且多样化。本节课程将介绍如何利用音视频处理工具ffmpeg和moviepy，快速将课程内容转化为视频。\n",
    "\n",
    "## 🍁 课程目标\n",
    "学完本课程后，你将能够：\n",
    "- 了解 ffmpeg，并使用 ffmpeg 剪辑视频。\n",
    "\n",
    "## 📖 课程目录\n",
    "\n",
    "- [1. 原理介绍](#🧮-1-原理介绍)\n",
    "- [2. 代码实践](#🛠️-2-代码实践)\n",
    "    - [2.1. 环境准备](#21-环境准备)\n",
    "    - [2.2. 剪辑视频](#22-剪辑视频)\n",
    "    - [2.3. 嵌入音频和字幕](#23-嵌入音频和字幕)\n",
    "  \n",
    "\n",
    "## 🧮 1. 原理介绍\n",
    "\n",
    "当前的大模型文生视频或图生视频方案还不足以直接生成符合我们预期的科普课程视频，因此，我们会采用传统的方案，使用音视频处理工具进行合成。 本次课程除了上次课程用到的 moviepy 外，你还将用到以下工具：\n",
    "\n",
    "- [ffmpeg](https://www.ffmpeg.org/)：一个开源的跨平台音视频处理工具，它提供了强大的音视频编解码功能、转换格式、录制和流媒体功能。FFmpeg 包含了丰富的命令行工具和库，使用户能够灵活地处理各种媒体文件。\n",
    "\n",
    "使用 ffmpeg 和 moviepy 将课程内容转换为视频的过程如下：\n",
    "\n",
    "<div align=\"center\">\n",
    "    <img src=\"https://gw.alicdn.com/imgextra/i4/O1CN01Q1FoKK1txOihXKrCX_!!6000000005968-0-tps-1684-164.jpg\" alt=\"流程\" width=\"60%\"/>\n",
    "</div>\n",
    "\n",
    "\n",
    "## 🛠️ 2. 代码实践\n",
    "\n",
    "接下来，让我们执行以下代码，将第一节课生成的内容转换为音频，并生成字幕。\n",
    "\n",
    "### 2.1. 环境准备\n",
    "1. 安装 ffmpeg。\n",
    "\n",
    "可以取消这里的注释来安装 ffmpeg库\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-09-29T10:55:24.485706Z",
     "iopub.status.busy": "2024-09-29T10:55:24.485399Z",
     "iopub.status.idle": "2024-09-29T10:55:28.128195Z",
     "shell.execute_reply": "2024-09-29T10:55:28.127588Z",
     "shell.execute_reply.started": "2024-09-29T10:55:24.485687Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !sudo apt update\n",
    "# !sudo apt install ffmpeg\n",
    "# !ffmpeg -version"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 安装 python 库。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-09-29T12:09:35.082193Z",
     "iopub.status.busy": "2024-09-29T12:09:35.081867Z",
     "iopub.status.idle": "2024-09-29T12:09:38.992974Z",
     "shell.execute_reply": "2024-09-29T12:09:38.992378Z",
     "shell.execute_reply.started": "2024-09-29T12:09:35.082173Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "! pip install -r requirements.txt -q"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 导入必要的模块。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-09-29T12:49:34.464747Z",
     "iopub.status.busy": "2024-09-29T12:49:34.464422Z",
     "iopub.status.idle": "2024-09-29T12:49:34.468197Z",
     "shell.execute_reply": "2024-09-29T12:49:34.467723Z",
     "shell.execute_reply.started": "2024-09-29T12:49:34.464729Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import json\n",
    "import re\n",
    "import time\n",
    "import traceback\n",
    "from pydub import AudioSegment\n",
    "from typing import List\n",
    "from moviepy.editor import *\n",
    "from PIL import Image\n",
    "import natsort\n",
    "import math\n",
    "import numpy as np\n",
    "from glob import glob\n",
    "import subprocess\n",
    "from utils import create_directory,read_text_from_file, save_file,load_config"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "### 2.2. 剪辑视频\n",
    "\n",
    "首先，我们将 PPT 剪辑为视频。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "project_config = load_config(\"config.json\")\n",
    "title = project_config[\"title\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. 定义一个 `calculate_durations_for_each_image` 函数，用于计算每一张演示文稿在视频中的持续时间。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-09-29T12:09:38.998398Z",
     "iopub.status.busy": "2024-09-29T12:09:38.998163Z",
     "iopub.status.idle": "2024-09-29T12:09:39.003077Z",
     "shell.execute_reply": "2024-09-29T12:09:39.002634Z",
     "shell.execute_reply.started": "2024-09-29T12:09:38.998382Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def calculate_audio_durations(directory):\n",
    "    \"\"\"\n",
    "    计算指定目录下所有以 audio_for_paragraph_{index} 命名的文件夹中 mp3 文件的总持续时间（以秒为单位）。\n",
    "\n",
    "    参数:\n",
    "        directory (str): 需要扫描的根目录路径。\n",
    "\n",
    "    返回:\n",
    "        list: 每个 audio_for_paragraph_{index} 文件夹中 mp3 文件总持续时间（秒）的列表。\n",
    "    \"\"\"\n",
    "    # 初始化结果列表\n",
    "    durations = []\n",
    "\n",
    "    # 遍历目录下的所有子目录\n",
    "    for entry in os.scandir(directory):\n",
    "        if entry.is_dir() and entry.name.startswith(\"audio_for_paragraph_\"):\n",
    "            # 提取 index\n",
    "            index = int(entry.name.split(\"_\")[-1])\n",
    "\n",
    "            # 初始化当前文件夹的总持续时间为0\n",
    "            total_duration_ms = 0\n",
    "\n",
    "            # 遍历子目录中的所有文件\n",
    "            for file_entry in os.scandir(entry.path):\n",
    "                if file_entry.name.endswith(\".mp3\"):\n",
    "                    # 加载 mp3 文件并计算持续时间\n",
    "                    audio = AudioSegment.from_mp3(file_entry.path)\n",
    "                    delay = 300\n",
    "                    total_duration_ms += len(audio) + delay\n",
    "\n",
    "            # 将当前文件夹的总持续\n",
    "            # 时间转换为秒，并添加到结果列表中\n",
    "            total_duration_seconds = total_duration_ms / 1000.0\n",
    "            durations.append((index, total_duration_seconds))\n",
    "\n",
    "    # 按照 index 排序结果列表\n",
    "    durations.sort(key=lambda x: x[0])\n",
    "\n",
    "\n",
    "\n",
    "    # 只保留持续时间（秒）\n",
    "    durations = [duration for _, duration in durations]\n",
    "\n",
    "    durations.insert(0, 2)\n",
    "\n",
    "    return durations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 调用 `calculate_durations_for_each_image` 函数计算每一张演示文稿在视频中的持续时间。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-09-29T12:20:38.183310Z",
     "iopub.status.busy": "2024-09-29T12:20:38.182796Z",
     "iopub.status.idle": "2024-09-29T12:20:42.195005Z",
     "shell.execute_reply": "2024-09-29T12:20:42.194508Z",
     "shell.execute_reply.started": "2024-09-29T12:20:38.183275Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 计算各段落的所有音频时长\n",
    "audio_file_folder = project_config[\"audio_file_folder\"].format(title=project_config[\"title\"])\n",
    "\n",
    "# 计算音频时长\n",
    "durations = calculate_audio_durations(audio_file_folder)\n",
    "\n",
    "durations_file=project_config[\"durations_folder\"].format(title=project_config[\"title\"])\n",
    "\n",
    "create_directory(durations_file)\n",
    "\n",
    "# 打印结果\n",
    "print(\"各段落的音频时长（秒）:\")\n",
    "with open(durations_file, \"w\") as f:\n",
    "    for index, duration in enumerate(durations):\n",
    "        f.write(f\"段落 {index + 1}: {duration:.2f} 秒\\n\")\n",
    "        print(f\"段落 {index + 1}: {duration:.2f} 秒\")\n",
    "\n",
    "print(f\"时长信息已保存到 {durations_file}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 定义一个 `images_to_video_with_durations` 函数，用于将所有输入演示文稿按顺序剪辑为视频。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2024-09-29T12:20:38.175574Z",
     "iopub.status.busy": "2024-09-29T12:20:38.175339Z",
     "iopub.status.idle": "2024-09-29T12:20:38.182196Z",
     "shell.execute_reply": "2024-09-29T12:20:38.181781Z",
     "shell.execute_reply.started": "2024-09-29T12:20:38.175557Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def images_to_video_with_durations(input_image_path, output_video_path, durations, fps, base_name):\n",
    "    # 获取所有符合条件的图片，并按文件名中的数字排序\n",
    "    # pattern = r'^' + re.escape(base_name) + r'_(\\d+)\\.png$'\n",
    "    pattern = r\".*_(\\d+)\\.png\"\n",
    "    image_files = [\n",
    "        f\"{input_image_path}/{file}\"\n",
    "        for file in os.listdir(input_image_path)\n",
    "        if re.match(pattern, file)\n",
    "    ]\n",
    "\n",
    "    print(\"Matching files:\", image_files)  # 调试输出，查看匹配的文件\n",
    "\n",
    "    image_files = natsort.natsorted(image_files, key=lambda x: int(re.match(pattern, os.path.basename(x)).group(1)))\n",
    "\n",
    "    # 确定视频的背景尺寸\n",
    "    target_width, target_height = 1280, 720\n",
    "    background_size = (target_width, target_height)\n",
    "\n",
    "    clips = []\n",
    "    for i, file in enumerate(image_files):\n",
    "        print(f\"Processing file: {file}, duration: {durations[i]}\")  # 再次调试输出\n",
    "        img = Image.open(file)\n",
    "        width, height = img.size\n",
    "        ratio = width / height\n",
    "\n",
    "        if width > target_width or height > target_height:\n",
    "            if ratio > target_width / target_height:\n",
    "                new_width = target_width\n",
    "                new_height = math.floor(new_width / ratio)\n",
    "            else:\n",
    "                new_height = target_height\n",
    "                new_width = math.floor(new_height * ratio)\n",
    "        else:\n",
    "            new_width, new_height = width, height\n",
    "\n",
    "        img = img.resize((new_width, new_height), resample=Image.Resampling.LANCZOS)\n",
    "        img_clip = ImageClip(np.array(img)).set_duration(durations[i])\n",
    "        img_clip = img_clip.set_position('center')\n",
    "        bg_clip = ColorClip(size=background_size, color=(255, 255, 255), duration=durations[i])\n",
    "\n",
    "        composite_clip = CompositeVideoClip([bg_clip, img_clip])\n",
    "        clips.append(composite_clip)\n",
    "\n",
    "    # 使用concatenate_videoclips函数将所有剪辑串联在一起\n",
    "    final_clip = concatenate_videoclips(clips, method=\"compose\")\n",
    "    output_filename = os.path.join(output_video_path, f\"{base_name}.mp4\")\n",
    "    create_directory(output_filename)\n",
    "    final_clip.write_videofile(output_filename, fps=fps)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4. 调用 `images_to_video_with_durations` 将 PPT 按顺序剪辑为视频。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-09-29T12:09:44.529858Z",
     "iopub.status.busy": "2024-09-29T12:09:44.529637Z",
     "iopub.status.idle": "2024-09-29T12:20:38.174308Z",
     "shell.execute_reply": "2024-09-29T12:20:38.173821Z",
     "shell.execute_reply.started": "2024-09-29T12:09:44.529832Z"
    },
    "scrolled": true,
    "tags": []
   },
   "outputs": [],
   "source": [
    "marp_export_image_folder = project_config[\"marp_export_image_folder\"].format(title=project_config[\"title\"])\n",
    "srt_and_video_folder = project_config[\"srt_and_video_folder\"]\n",
    "fps =  project_config[\"fps\"]\n",
    "\n",
    "# 检查输入图像\n",
    "# pattern = r'^' + re.escape(input_base_name) + r'_(\\d+)\\.png$'\n",
    "pattern = r\".*_(\\d+)\\.png\"\n",
    "\n",
    "image_files = [\n",
    "    f\"{marp_export_image_folder}/{file}\"\n",
    "    for file in os.listdir(marp_export_image_folder)\n",
    "    if re.match(pattern, file)\n",
    "]\n",
    "\n",
    "print(\"Matching files:\", image_files)  # 输出匹配的文件\n",
    "\n",
    "if not image_files:\n",
    "    raise ValueError(\"No matching image files found.\")\n",
    "\n",
    "# 检查 durations 的数量\n",
    "if len(durations) != len(image_files):\n",
    "    raise ValueError(\"The number of durations must match the number of image files.\")\n",
    "\n",
    "# 调用函数\n",
    "images_to_video_with_durations(\n",
    "    marp_export_image_folder,\n",
    "    srt_and_video_folder,\n",
    "    durations,\n",
    "    fps,\n",
    "    project_config[\"title\"]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "### 2.3. 嵌入音频和字幕\n",
    "\n",
    "接下来，我们将上一课制作的音频和字幕添加到视频中。\n",
    "\n",
    "1. 定义一个 `merge_audio_and_add_to_video` 函数，用于合成音频并将音频添加到视频中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.status.busy": "2024-09-29T12:43:57.610134Z",
     "iopub.status.idle": "2024-09-29T12:43:57.610356Z",
     "shell.execute_reply": "2024-09-29T12:43:57.610265Z",
     "shell.execute_reply.started": "2024-09-29T12:43:57.610256Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def merge_audio_and_add_to_video(video_path, audio_base_dir, output_path):\n",
    "    \"\"\"\n",
    "    合并多个音频文件并添加到视频中。\n",
    "\n",
    "    :param video_path: 视频文件的路径。\n",
    "    :param audio_base_dir: 包含音频文件夹的基目录。\n",
    "    :param output_path: 输出视频的路径。\n",
    "    \"\"\"\n",
    "    # 加载视频文件\n",
    "    video_clip = VideoFileClip(video_path)\n",
    "\n",
    "    # 初始化音频列表\n",
    "    audio_clips = []\n",
    "\n",
    "    silent_audio_start = AudioClip(lambda t: [0,0], duration=2)\n",
    "    audio_clips.append(silent_audio_start)\n",
    "\n",
    "    # 遍历所有子目录，按数字大小排序\n",
    "    audio_dirs = glob(os.path.join(audio_base_dir, \"audio_for_paragraph_*\"))\n",
    "    audio_dirs.sort(key=lambda x: int(re.search(r'\\d+', os.path.basename(x)).group()))\n",
    "\n",
    "    # 遍历所有子目录\n",
    "    for audio_dir in audio_dirs:\n",
    "        # 获取当前目录的index\n",
    "        index = int(os.path.basename(audio_dir).split(\"_\")[-1])\n",
    "\n",
    "        # 遍历目录中的所有mp3文件\n",
    "        mp3_files = glob(os.path.join(audio_dir, f\"paragraph_{index}_sentence_*.mp3\"))\n",
    "        mp3_files.sort(key=lambda x: int(re.search(r'_sentence_(\\d+)', os.path.basename(x)).group(1)))\n",
    "\n",
    "        # 遍历排序后的mp3文件列表\n",
    "        for mp3_file in mp3_files:\n",
    "            # 加载音频文件\n",
    "            audio_clip = AudioFileClip(mp3_file)\n",
    "\n",
    "            # 添加到音频列表\n",
    "            if audio_clips:\n",
    "                # 如果不是第一个音频，则在前一个音频之后添加0.5秒的静音\n",
    "                # 替换原有的 AudioNullClip 代码\n",
    "                silent_audio = AudioClip(lambda t: [0,0], duration=0.3)\n",
    "                audio_clips.append(silent_audio)\n",
    "            audio_clips.append(audio_clip)\n",
    "\n",
    "    # 合并所有音频片段\n",
    "    final_audio = concatenate_audioclips(audio_clips)\n",
    "\n",
    "    # 将音频添加到视频中\n",
    "    video_with_audio = video_clip.set_audio(final_audio)\n",
    "\n",
    "    # 输出带有新音频的视频文件\n",
    "    video_with_audio.write_videofile(output_path, codec='libx264', audio_codec='aac')\n",
    "\n",
    "    # 关闭剪辑对象，释放资源\n",
    "    video_clip.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 调用函数 `merge_audio_and_add_to_video` 添加音频。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 合成路径\n",
    "video_raw = project_config[\"video_raw\"].format(title=project_config[\"title\"])# 视频文件的路径\n",
    "audio_file_folder = project_config[\"audio_file_folder\"].format(title=project_config[\"title\"])\n",
    "# audio_base_dir = \"./output/audio/\"+title+\"_课程脚本_speech_script_plus\"  # 音频文件夹的基目录\n",
    "video_with_audio = project_config[\"video_with_audio\"].format(title=project_config[\"title\"]) # 输出视频的路径\n",
    "\n",
    "# 检查视频和音频路径是否存在\n",
    "if not os.path.exists(video_raw):\n",
    "    raise ValueError(f\"Video file not found: {video_raw}\")\n",
    "\n",
    "if not os.path.exists(audio_file_folder):\n",
    "    raise ValueError(f\"Audio directory not found: {audio_file_folder}\")\n",
    "\n",
    "# 调用函数\n",
    "merge_audio_and_add_to_video(video_raw, audio_file_folder, video_with_audio)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 定义一个 `merge_video_and_subtitle` 函数，用于将字幕添加到视频中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-09-29T12:58:34.213776Z",
     "iopub.status.busy": "2024-09-29T12:58:34.213408Z",
     "iopub.status.idle": "2024-09-29T12:58:34.217078Z",
     "shell.execute_reply": "2024-09-29T12:58:34.216641Z",
     "shell.execute_reply.started": "2024-09-29T12:58:34.213755Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def merge_video_and_subtitle(video_path, srt_path, output_path):\n",
    "    # 如果输出文件已存在，删除它\n",
    "    if os.path.exists(output_path):\n",
    "        os.remove(output_path)\n",
    "\n",
    "    command = [\n",
    "        'ffmpeg',\n",
    "        '-i', video_path,\n",
    "        '-vf', f'subtitles={srt_path}',\n",
    "        '-c:a', 'copy',\n",
    "        output_path\n",
    "    ]\n",
    "\n",
    "    subprocess.run(command, check=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4. 调用 `merge_video_and_subtitle` 函数将字幕添加到视频中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-09-29T12:58:54.750953Z",
     "iopub.status.busy": "2024-09-29T12:58:54.750615Z",
     "iopub.status.idle": "2024-09-29T12:59:02.721706Z",
     "shell.execute_reply": "2024-09-29T12:59:02.721223Z",
     "shell.execute_reply.started": "2024-09-29T12:58:54.750932Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 构建文件路径\n",
    "video_with_audio = project_config[\"video_with_audio\"].format(title=project_config[\"title\"])\n",
    "srt_file_path = project_config[\"srt_file_path\"].format(title=project_config[\"title\"])\n",
    "\n",
    "video_with_audio_and_subtitles = project_config[\"video_with_audio_and_subtitles\"].format(title=project_config[\"title\"])\n",
    "\n",
    "# 调用函数\n",
    "merge_video_and_subtitle(video_with_audio, srt_file_path, video_with_audio_and_subtitles)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "5. 播放生成好的视频"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Video\n",
    "\n",
    "# 本地视频文件路径\n",
    "video_path = project_config[\"video_with_audio_and_subtitles\"].format(title=project_config[\"title\"])\n",
    "print(\"视频地址：\",video_path)\n",
    "\n",
    "# 播放视频\n",
    "Video(video_path, width=768, height=512)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "## ✅ 本节小结\n",
    "\n",
    "- 在本次学习和实践中，我们了解了 ffmpeg，并使用该工具生成了视频。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 🔥 课后小测验\n",
    "\n",
    "【多选题】3.5.1. 整合大模型生成的视频、音频和字幕时，我们可能会遇到哪些挑战？（多选）\n",
    "\n",
    "A. 生成的视频画面与音频或字幕的节奏不匹配。\n",
    "\n",
    "B. 字幕翻译不准确。\n",
    "\n",
    "C. 存在杂音或失真，影响观感。\n",
    "\n",
    "D. 计算资源消耗过大，生成速度慢。\n",
    "\n",
    "E. 确保所有元素版权合规，且大模型生成的内容仍有可能存在版权风险。\n",
    "\n",
    "答案：A，B，C, D，E"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ✉️ 评价反馈\n",
    "感谢你学习阿里云大模型ACP认证课程，如果你觉得课程有哪里写得好、哪里写得不好，期待你[通过问卷提交评价和反馈](https://survey.aliyun.com/apps/zhiliao/Mo5O9vuie)。\n",
    "\n",
    "你的批评和鼓励都是我们前进的动力。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "learnacp",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
